Notes
{labels: 'NIL, viewFont: 10241} // nil=Unfiled, or specify a folder, e.g., 'Business.  remove viewFont for current Styles default
//ERASE! remove initial // to erase existing entries in this folder first

life.nwt -- Slurpee format
1/1/96
Copyright 1994-96. S. Weyer.  All Rights Reserved Worldwide.

Newt source (verson 1.4) for "the game of Life"
See life.txt & online help book for description 

(optional) life.bit for Bitmaps: LifeIcon
(optional) LifeCnst.pkg plug-in for native methods ("arrN", "bitN")
----------
init
func()
begin // several constants
	:DefConst('kFirstRowCol,1000);
	:DefConst('kLastRowCol,-1);
end
----------
Life
//:doObj('add, 'Life)
//:doObj('remove, 'Life)
//:doObj('build, 'Life)
{// the main Life application
viewClass: clView,
viewBounds: NIL,    // computed in viewSetupFormScript
viewFlags: 4,       // vApplication
viewFormat: 337,    // vfFillWhite + vfFrameBlack + vfPen*1,
declareSelf: 'base, // for close box
title: "Life 1.4",

// sizeX can be <= 30. others computed in viewSetupFormScript
sizeX:     26, // initial width of universe. right bits of a longint to use [ <= 30]
               // value of sizeX should also be included in xSizes list (see gridPicker)

// these are computed from sizeX at run-time in viewSetupFormScript
sizeY:    NIL, // height of universe.
cellSize: NIL, // size of a cell. maximize for screen width (maxAppWidth DIV sizeX)
vSizeX:   NIL, // width of drawing view (cellSize*sizeX)  [<= maxAppWidth]
vSizeY:   NIL, // height of drawing view
offsetX:  NIL, // number of bit columns on left to skip (sizeX-32 *cellSize)
maxAppWidth:   NIL,
maxDrawHeight: NIL,

firstRow: NIL, // to avoid dead rows above
lastRow:  NIL, // to avoid dead rows below
firstCol: NIL, // ditto for columns
lastCol:  NIL,
pattern:  NIL, // defaults to dot. set by patternPicker
nGen:      0,  // number of generations (clear =0, nextGeneration +1)
universe: NIL, // representation of cells (array or bitmap)
curMethod:  0, // arr*,arrI,arrN,bit,bitI,bitN.  set by methodPicker

kDrawTop: 20,  // extra space above drawArea (for title, gridPicker)
kDrawBot: 35,  // below (for patternPicker, genPicker, status)

viewSetupFormScript: func()
	begin
		self.Life := self; // for run-time access (rather than :parent())
		GetRoot().ExtrasDrawer:?close(); // get it out of the way. save a little heap
		local ht, ap:= GetAppParams();   // configure to use entire current MP screen size
		self.viewBounds := RelBounds(
			ap.AppAreaLeft,ap.AppAreaTop,
			maxAppWidth := ap.AppAreaWidth,
			ht := ap.AppAreaHeight);
		maxDrawHeight := ht - kDrawTop - kDrawBot;
		stopped := true;
		:setGrid(sizeX,nil);
	end,
	
setGrid: func(x,redo) // intialize or resize drawing area
	begin
		universe := NIL;
		sizeX    := x;
		cellSize := maxAppWidth DIV sizeX;
		sizeY    := maxDrawHeight DIV cellSize;
		vSizeX   := cellSize * sizeX; // <= maxAppWidth. if sizeX divided evenly, no border
		vSizeY   := cellSize * sizeY; // <= maxDrawHeight
		offsetX  := (sizeX-32) * cellSize; // left bits not shown
		:clear();
		if redo then :RedoChildren();
	end,

clear: func()
	begin
		:setUniverse(:makeUniverse());
		nGen := 0;
		firstRow := firstCol := kFirstRowCol; // all empty
		lastRow  := lastCol  := kLastRowCol;
		if drawArea exists
		then drawArea:?Dirty(); // redraw next idle via viewDrawScript
	end,

makeUniverse: func()
	Array(sizeY, 0), // leave rows 0 until needed

setUniverse: func(u)
	universe := u,

_package: {
	icon: :GetPictAsBits("Bitmaps","LifeIcon"), // comment out if life.bit not transferred
	},
}
----------
Life.init1~
{// this gets added to app frame but a little easier for editing if it's separate

helpBook: :helpBookTemplate(), // copy data structure from Newt at development-time
// actual help pages are added later, i.e., Life.helpBook+page1, etc.

helpView: NIL,

viewQuitScript: func()
	begin
	if helpView then helpView:close();
	helpView := universe := NIL;
	end,

// to support Rotate for Newton OS 2.0
//reorienting: NIL,
ReorientToScreen: func()
	begin
		//reorienting := TRUE;
		:SyncView();
		:RedoChildren();
		//reorienting := NIL;
	end,
}
----------
Life.idleStuff~
{// some additional Life methods/slots related to idling ("Repeat")
updateGen: TRUE, // to refresh gen counter during Repeat (default). set via genPicker

nCount: NIL, // num of gen for timing tests. e.g., 40.  set by genPicker

viewIdleScript: func()
	if not stopped
	then begin
		if :nextGeneration() // generate
		then begin
			drawArea:drawDots(true);
			if updateGen
			then genPicker:updateText(NumberStr(nGen));
			end;

		if firstRow < kFirstRowCol and (not nCount or nGen < nCount)
		then delayMSecs // reschedule
		else status.repeatButton:stop(nil); // empty universe. NIL
		end,

delayMsecs: 5, // allow a few milliseconds for taps (e.g., Stop, Clear, Gen:)
stopped: TRUE,
}
----------
Life.arr~
{// these are added to app frame. also "copied" into _arr later
// use an array for representation, draw dots explicitly
//drawDots -- defined separately

getCell: func(row,x)
	if row=0 // or x < 0 or x >= sizeX
	then 0
	else row[x],

getRow:	func(univ,y)
	univ[y],

getUniverse: func()
	universe,

// makeUniverse -- defined earlier

// nextGeneration -- defined separately

setCell: func(row,x,b)
	begin
		if row=0
		then row := Array(sizeX, 0);
		row[x] := if b then b else 1 - row[x];
		row;
	end,

setRow: func(univ,y,row)
	univ[y] := row,

// setUniverse -- defined earlier
}
----------
Life.drawDots
func(idle)
if idle
then :dirty() // let viewDrawScript do it

else if firstRow < kFirstRowCol
then begin
	local drawMode := '{ // for ovals
		transferMode: 0, // modeCopy
		fillPattern:  5, // vfFillBlack
		};
	local y,row,y1,y2,x,x1;
	for y := firstRow to lastRow
	do if isArray(row := universe[y]) // :getRow(universe,y)
		then begin
			y1 := y*cellSize; y2 := y1+cellSize; // constant for row
			for x := firstCol to lastCol
			do if row[x]>0 // :getCell(row,x)
			then begin
				x1 := (sizeX-x-1)*cellSize;
				:DrawShape(MakeOval(x1,y1,x1+cellSize,y2),drawMode);
				end;
			end;
	end
----------
Life.setCells
func(row,x,vals,start,count) // generic version used by drawArea.viewGestureScript
begin
	local vx, vlen := 0, fill := 0;
	if isArray(vals)
	then vlen := Length(vals)
	else fill := vals;
	for vx := start to start+count-1
	do row := :setCell(row,x-vx,if vx < vlen then vals[vx] else fill);
	row;
end
----------
Life.nextGeneration
func() // generic method (used by "arr" & "bit")
if firstRow < kFirstRowCol // only bother if there are some cells
then begin
	local univ := :getUniverse();     	// existing
	local newuniv := :makeUniverse();	// new, empty universe
	local x, y, neighbors, frx, lrx;

	local frow := firstRow-1, lrow := lastRow+1;
	if frow<0 then frow := 0;
	if lrow >= sizeY then lrow := sizeY-1;

	local fcol := firstCol-1, lcol := lastCol+1;
	if fcol<0 then fcol := 0;
	if lcol >= sizeX then lcol := sizeX-1;

	local newFirstRow := kFirstRowCol, newLastRow := kLastRowCol;
	local newFirstCol := kFirstRowCol, newLastCol := kLastRowCol;

	local n1, n2, n3; // in this layout visually.
	local n4, n5, n6;
	local n7, n8, n9;
	local row1 := 0 , row2 := 0, newRow := 0;
	local row3 := :getRow(univ,frow);

	// process from top to bottom, from right to left
	for y := frow to lrow // skip some dead rows before/after active area
	do begin
		row1 := row2; row2 := row3;
		row3 := if y<lrow then :getRow(univ,y+1) else 0;
		if row1<>0 or row2<>0 or row3<>0
		then begin // this optimizes for empty row(s)
			frx := kFirstRowCol; lrx := kLastRowCol;
			n1 := :getCell(row1,fcol);
			n4 := :getCell(row2,fcol);
			n7 := :getCell(row3,fcol);
			newRow := n2 := n5 := n8 := n3 := n6 := n9 := 0;
			for x := fcol to lcol // skip some dead columns after/before active area
			do begin
				if row1<>0
				then begin
					n3 := n2; n2 := n1;
					n1 := if x=lcol then 0 else :getCell(row1,x+1);
					neighbors := n1+n2+n3;
					end
				else neighbors := 0;
				if row2<>0
				then begin
					n6 := n5; n5 := n4;
					n4 := if x=lcol then 0 else :getCell(row2,x+1);
					neighbors := neighbors + n4+n6; // omit center
					end;
				if row3<>0
				then begin
					n9 := n8; n8 := n7;
					n7 := if x=lcol then 0 else :getCell(row3,x+1);
					neighbors := neighbors + n7+n8+n9;
					end;
				if neighbors=3 or (neighbors=2 and n5=1)
				then begin
					newRow := :setCell(newRow,x,1);
					if x < frx then frx := x; // 1st(min)
					lrx := x;
					end;
				end;
			if lrx > kLastRowCol // something set in row?
			then begin
				:setRow(newuniv,y,newRow);
				newLastRow := y; // last(max)
				if   y < newFirstRow then newFirstRow := y; // 1st(min)
				if frx < newFirstCol then newFirstCol := frx; // min
				if lrx > newLastCol  then newLastCol  := lrx; // max
				end;
			end;
		end;
	firstRow := newFirstRow; lastRow := newLastRow;
	firstCol := newFirstCol; lastCol := newLastCol;
	:setUniverse(newuniv);
	nGen := nGen+1; // <true>
	// refresh done via viewIdleScript or Next
end
----------
Life+drawArea
{// a drawing area (stepchild of Life)
viewClass: clView,
viewFlags:  2561, // vVisible + vClickable + vGesturesAllowed
viewJustify:  16, // vjParentCenterH + vjParentTopV
viewFormat:  337, // vfFillWhite + vfFrameBlack + vfPen*1,

viewSetupFormScript: func()
	self.viewBounds := RelBounds(0,kDrawTop,vSizeX,vSizeY),

viewDrawScript: func()
	begin // this is only invoked via :dirty (and it's stopped)
		:drawDots(nil); // (re)defined by arr/bit
		if stopped
		then begin // draw grid
			local x, y;
			local drawMode := '{ // for lines
				penSize: 1,
				penPattern: 3, // vfGray
				};
			for y := cellSize to vSizeY-cellSize by cellSize 	// horizontal lines
			do :DrawShape(MakeLine(0,y,vSizeX,y), drawMode);
			for x := cellSize-1 to vSizeX-cellSize by cellSize 	// vertical lines
			do :DrawShape(MakeLine(x,0,x,vSizeY), drawMode);

			if updateGen and genPicker exists // might not during very initial build
			then genPicker:updateText(NumberStr(nGen));
			end;
	end,
}
----------
Life.drawArea.viewGestureScript
func(unit, gestureID)
begin
	local gb := :GlobalBox(), univ := :getUniverse();
	local sb := StrokeBounds(unit);
	local ylen := if pattern then Length(pattern) else 0;
	// convert to local, grid coordinates. flip x
	local maxX := sizeX-1, maxY := sizeY-1;
	local x1 := maxX - max(0,    (sb.left   - gb.left) div cellSize);
	local y1 :=        max(0,    (sb.top    - gb.top)  div cellSize);
	local x2 := maxX - min(maxX, (sb.right  - gb.left) div cellSize);
	local y2 :=        min(maxY, (sb.bottom - gb.top)  div cellSize);
	local xlen := x1-x2+1, y, fill := 0;

	if gestureID=13	or				// aeScrub
		(gestureID=16 and fill:=1)	// aeLine
	then for y := y1 to y2	// fill area
		do :setRow(univ,y, :setCells(:getRow(univ,y),x1, fill,0,xlen))

	else if ylen>0
	then begin // copy a pattern
		if y2+ylen > sizeY
		then ylen := sizeY - y2; // clip pattern at bottom
		y2 := y2 + ylen - 1;
		xlen := Length(pattern[0]);  // note: uses length of first row as max width of pattern
		if x2-xlen < -1
		then xlen := x2+1; // clip pattern at right
		x2 := x2 - xlen + 1;
		for y := 0 to ylen-1
		do :setRow(univ, y+y1, :setCells(:getRow(univ,y+y1),x1, pattern[y],0,xlen));
		end

	else // toggle single cell
		:setRow(univ,y1, :setCell(:getRow(univ,y1),x1,NIL));

	// expand live area
	if y1 < firstRow then firstRow := y1;
	if y2 > lastRow  then lastRow  := y2;
	if x2 < firstCol then firstCol := x2;
	if x1 > lastCol  then lastCol  := x1;

	:Dirty(); // returns true
end
----------
Life._arr
{// this is the default version, so just copy the built-in versions
drawDots: 		Life.drawDots,
getCell: 		Life.getCell,
getRow:			Life.getRow,
getUniverse:	Life.getUniverse,
makeUniverse:	Life.makeUniverse,
nextGeneration:	Life.nextGeneration,
setCell:		Life.setCell,
setRow:			Life.setRow,
setUniverse:	Life.setUniverse,
}
----------
Life._arrI
{// same as _arr, but nextGeneration inlined
drawDots: 		_arr.drawDots,
getCell: 		_arr.getCell,
getRow:			_arr.getRow,
getUniverse:	_arr.getUniverse,
makeUniverse:	_arr.makeUniverse,
nextGeneration:	_arr.nextGeneration, // redefine (next)
setCell:		_arr.setCell,
setRow:			_arr.setRow,
setUniverse:	_arr.setUniverse,
}
----------
Life._arrN
{// same as _arr but nextGeneration native
drawDots: 		_arr.drawDots,
getCell: 		_arr.getCell,
getRow:			_arr.getRow,
getUniverse:	_arr.getUniverse,
makeUniverse:	_arr.makeUniverse,
nextGeneration: if arrN_nextGeneration exists then arrN_nextGeneration else _arr.nextGeneration,
setCell:		_arr.setCell,
setRow:			_arr.setRow,
setUniverse:	_arr.setUniverse,
}
----------
Life._arrI.nextGeneration
func() // inlined version. ("arrN" adds INT,ARRAY declarations)
if firstRow < kFirstRowCol
then begin
	local univ := universe;
	local newuniv := Array(sizeY,0);
	local x, y, neighbors, frx, lrx;

	local frow := firstRow-1, lrow := lastRow+1;
	if frow<0 then frow := 0;
	if lrow >= sizeY then lrow := sizeY-1;

	local fcol := firstCol-1, lcol := lastCol+1;
	if fcol<0 then fcol := 0;
	if lcol >= sizeX then lcol := sizeX-1;

	local newFirstRow := kFirstRowCol, newLastRow := kLastRowCol;
	local newFirstCol := kFirstRowCol, newLastCol := kLastRowCol;

	local n1, n2, n3;
	local n4, n5, n6;
	local n7, n8, n9;
	local row1 := 0 , row2 := 0, newRow := 0;
	local row3 := univ[frow];

	for y := frow to lrow
	do begin
		row1 := row2; row2 := row3;
		row3 := if y<lrow then univ[y+1] else 0;
		if row1<>0 or row2<>0 or row3<>0
		then begin
			frx := kFirstRowCol; lrx := kLastRowCol;
			n1 := if row1=0 then 0 else row1[fcol];
			n4 := if row2=0 then 0 else row2[fcol];
			n7 := if row3=0 then 0 else row3[fcol];
			newRow := n2 := n5 := n8 := n3 := n6 := n9 := 0;
			for x := fcol to lcol
			do begin
				if row1<>0
				then begin
					n3 := n2; n2 := n1;
					n1 := if x=lcol or row1=0 then 0 else row1[x+1];
					neighbors := n1+n2+n3;
					end
				else neighbors := 0;
				if row2<>0
				then begin
					n6 := n5; n5 := n4;
					n4 := if x=lcol or row2=0 then 0 else row2[x+1];
					neighbors := neighbors + n4+n6;
					end;
				if row3<>0
				then begin
					n9 := n8; n8 := n7;
					n7 := if x=lcol or row3=0 then 0 else row3[x+1];
					neighbors := neighbors + n7+n8+n9;
					end;
				if neighbors=3 or (neighbors=2 and n5=1)
				then begin
					if newRow=0
					then newRow := Array(sizeX, 0);
					newRow[x] := 1;
					if x < frx then frx := x;
					lrx := x;
					end;
				end;
			if lrx > kLastRowCol
			then begin
				newuniv[y] := newRow;
				newLastRow := y;
				if   y < newFirstRow then newFirstRow := y;
				if frx < newFirstCol then newFirstCol := frx;
				if lrx > newLastCol  then newLastCol  := lrx;
				end;
			end;
		end;
	firstRow := newFirstRow; lastRow := newLastRow;
	firstCol := newFirstCol; lastCol := newLastCol;
	universe := newuniv;
	nGen := nGen+1;
end
----------
Life._bit
{// use a bitmap representation
drawDots: func(idle) // this is sent to drawArea from viewIdleScript or viewDrawScript
	:copyBits(universe, offsetX, 0, nil),

getCell: func(row,x) // return a bit: 0 or 1
	// left shifting sign bit (x=29) fills 1's (so usually test first)
	if row=0 //or x >= sizeX
	then 0
	else BAND(row >> x, 1),   // 0 <= x < sizeX. 0 on right

getRow: func(bits,y) // return a row (30-bit integer) in bitmap. 0<=y<sizeY
	ExtractLong(bits, 16 + (y*4)),

getUniverse: func()  // return current bitmap
	universe.bits,

makeUniverse: func()
	begin // initialize a new bitmap
		local myBits := SetLength(Clone("\u00000000000000000000000000000000"),
							16 + (4 * sizeY)); //rowBytes*pixelHeight
		SetClass (myBits,'bits);
		StuffWord(myBits,  4, 4); 		// rowBytes
		StuffWord(myBits, 12, sizeY);	// pixelHeight
		StuffWord(myBits, 14, 32);		// pixelWidth
		myBits; 
	end,

nextGeneration: Life.nextGeneration, // generic version. same as _arr.nextGeneration

setCell: func(row,x,b) // set a bit to 0 or 1 (or NIL=toggle) & return new row
	begin // [used only by viewClickScript currently]
		local bit1 := 1 << x; // 0 <= x < sizeX. 0 on right
		if row=0 and b<>0  // 1, NIL
		then bit1
		else if (if b then b=0 else BAND(row, bit1) <> 0)
		then BAND(row, BNOT(bit1)) // :=0
		else BOR(row, bit1);       // :=1
	end,

setRow: func(bits,y,row) // replace a row (30-bit integer) in bitmap
	StuffLong(bits, 16 + (y*4), row),

setUniverse: func(u) // set current bitmap
	if isFrame(universe)
	then universe.bits := u
	else universe := { // a bitmap frame
		bounds: RelBounds(0, 0, 32*cellSize, vSizeY),
		bits:   u, // reset this each generation
		},
}
----------
Life._bitI
{// keep copies of refs
drawDots: 		_bit.drawDots,
getCell:		_bit.getCell,
getRow:			_bit.getRow,
getUniverse:	_bit.getUniverse,
makeUniverse:	_bit.makeUniverse,
nextGeneration:	_bit.nextGeneration, // redefine (next)
setCell:		_bit.setCell,
setRow:			_bit.setRow,
setUniverse:	_bit.setUniverse,
}
----------
Life._bitN
{// same as bit but with two native methods from plug-in LifeConst:TKnollSys
// nextGeneration and makeUniverse have some declarations and provide most of the benefit
drawDots: 		_bit.drawDots,
getCell:		_bit.getCell,
getRow:			_bit.getRow,
getUniverse:	_bit.getUniverse,
makeUniverse:	if bitN_makeUniverse exists then bitN_makeUniverse else _bit.makeUniverse,
nextGeneration:	if bitN_nextGeneration exists then bitN_nextGeneration else _bit.nextGeneration,
setCell:		_bit.setCell,
setRow:			_bit.setRow,
setUniverse:	_bit.setUniverse,
}
----------
Life._bitI.nextGeneration
func() // inline version. ("bitN" adds INT declarations)
if firstRow < kFirstRowCol
then begin
	local univ := universe.bits;
	local newuniv := :makeUniverse();
	// following locals are INT in native (bitN) version
	local x, y, neighbors, frx, lrx;
	local frow := firstRow-1, lrow := lastRow+1;
	if frow<0 then frow := 0;
	if lrow >= sizeY then lrow := sizeY-1;

	local fcol := firstCol-1, lcol := lastCol+1;
	if fcol<0 then fcol := 0;
	if lcol >= sizeX then lcol := sizeX-1;

	local newFirstRow := kFirstRowCol, newLastRow := kLastRowCol;
	local newFirstCol := kFirstRowCol, newLastCol := kLastRowCol;

	local n1, n2, n3;
	local n4, n5, n6;
	local n7, n8, n9;
	local row1 := 0 , row2 := 0, newRow := 0;
	local row3 := ExtractLong(univ, 16 + (frow*4));

	for y := frow to lrow
	do begin
		row1 := row2; row2 := row3;
		row3 := if y<lrow then ExtractLong(univ, 16 + (y+1)*4) else 0;
		if row1<>0 or row2<>0 or row3<>0
		then begin
			frx := kFirstRowCol; lrx := kLastRowCol;
			n1 := BAND(row1 >> fcol, 1);
			n4 := BAND(row2 >> fcol, 1);
			n7 := BAND(row3 >> fcol, 1);
			newRow := n2 := n5 := n8 := n3 := n6 := n9 := 0;
			for x := fcol to lcol
			do begin
				if row1<>0
				then begin
					n3 := n2; n2 := n1;
					n1 := if x=lcol or row1=0 then 0 else BAND(row1 >> (x+1), 1);
					neighbors := n1+n2+n3;
					end
				else neighbors := 0;
				if row2<>0
				then begin
					n6 := n5; n5 := n4;
					n4 := if x=lcol then 0 else BAND(row2 >> (x+1), 1);
					neighbors := neighbors + n4+n6;
					end;
				if row3<>0
				then begin
					n9 := n8; n8 := n7;
					n7 := if x=lcol then 0 else BAND(row3 >> (x+1), 1);
					neighbors := neighbors + n7+n8+n9;
					end;
				if neighbors=3 or (neighbors=2 and n5=1)
				then begin
					newRow := BOR(newRow, 1 << x);
					if x < frx then frx := x;
					lrx := x;
					end;
				end;
			if lrx > kLastRowCol
			then begin
				StuffLong(newuniv, 16 + (y*4), newRow);
				newLastRow := y;
				if   y < newFirstRow then newFirstRow := y;
				if frx < newFirstCol then newFirstCol := frx;
				if lrx > newLastCol  then newLastCol  := lrx;
				end;
			end;
		end;
	firstRow := newFirstRow; lastRow := newLastRow;
	firstCol := newFirstCol; lastCol := newLastCol;
	universe.bits := newuniv;
	nGen := nGen+1;
end
----------
Life+titleObj
{// title at top (displays Life.title)
_proto: protoTitle,
}
----------
Life+gridPicker
{// a picker for choosing size of grid
_proto: protoLabelPicker,
text: "grid",
viewBounds: RelBounds(1,1,100,14),
xSizes: [30,26,24,20,16,12], // some convenient sizes. max 30. include original sizeX
labelActionScript: func(i)
	Life:setGrid(xSizes[i],true),
textSetup: func() // current size (sizeX had better be in list)
	labelCommands[SetContains(xSizes,sizeX)],
viewSetupFormScript: func()
	begin
	local xsize;
	self.labelCommands :=
		foreach xsize in xSizes // e.g., 30x35
		collect xsize & "x" & maxDrawHeight DIV (maxAppWidth DIV xsize);
	inherited:?viewSetupFormScript();
	end,
}
----------
Life+methodPicker
{// a picker for chooosing various representation/display methods
_proto: protoLabelPicker,
text: "method",
viewBounds: RelBounds(-87,1,86,14),
labelCommands: ["Arr", "ArrI", "ArrN", "Bit", "BitI", "BitN",],
viewJustify: 32,    // vjParentRightH
labelActionScript: func(i)
	begin // splice in different versions of methods
		curMethod := i;
		local msg, meths := Life.(Intern("_" & labelCommands[i]));
		foreach msg in '[
			drawDots, getCell, getRow, getUniverse, makeUniverse,
			nextGeneration, setCell, setRow, setUniverse]
		do Life.(msg) := meths.(msg);
		:clear();
	end,
textSetup: func()
	labelCommands[curMethod],
}
----------
Life+patternPicker
{// a picker for pre-built patterns
_proto: protoLabelPicker,
viewBounds: RelBounds(1, -33, 135, 14),
text: "pattern",
viewJustify: 128 + 8388608, // vjParentBottomV + vjLeftH + oneLineOnly,
patterns: [// order matches names in labelCommands
	NIL,         // none=dot
	NIL,         // menu pickseparator line --------
	[[0,1,0,],   // glider (note: 1st line of pattern should be max width (pad right with 0s if nec)
	 [1,],       //   subsequent rows can omit trailing 0s
	 [1,1,1,]],
	[[1,1,1,]],  // blinker
	[[1,1],      // block
	 [1,1]],
	[[0,1,1,0,], // beehive
	 [1,0,0,1,],
	 [0,1,1,]],
	],
labelCommands: ["<dot>", 'pickSeparator, "glider", "blinker", "block", "beehive",],
labelActionScript: func(i)
	pattern := patterns[i], // for copying in drawArea.viewGestureScript
textSetup: func() // name of current pattern
	labelCommands[SetContains(patterns,pattern)],
}
----------
Life+genPicker
{// a "generation" count picker
_proto: protoLabelPicker,
text: "gen",
viewBounds: RelBounds(-105,-33,104,14), // at right, just above status bar
labelCommands: [
	"0", 			// update current gen# (nGen)
	"<0>",			// don't update
	"<time:40>",	// don't update. do timing&stop at 40
	"<time:100>",	// don't udpate. do timing&stop at 100
	],
viewJustify: 32+128,// vjParentRightH+vjParentBottomV
labelActionScript: func(i)
	begin
		updateGen := i=0;
		nCount :=
			if i = 2
			then 40
			else if i=3
			then 100
			else NIL;
		nGen := 0;
	end,
textSetup: func() // name of current gen option
	labelCommands[
		if ncount=40 then 2
		else if ncount=100 then 3
		else if updateGen then 0
		else 1],
}
----------
ButtonBounds
func(width)
// used for adding embedded buttons to status
// this is defined as a Newt development-time method
// once the buttons are initialized and added to status,
// it really isn't needed anymore (and it isn't saved with the package)
// also not needed if ViewCnst.pkg plug-in is used
begin
	local left1:=25, top:=2, spacing:=6, bottom:=15;
	if width > 0
	then SetBounds(spacing,  top, spacing + width, bottom)   // other buttons
	else SetBounds(left1,    top, left1 - width,   bottom);  // first button
end
----------
Life+status
{// status bar with clock and closebox
_proto: protoStatus,
}
----------
Life.status+clearButton
{_proto: protoTextButton,
viewBounds: :ButtonBounds(-40), // first button (stepchild) in status area
text: "Clear",
buttonClickScript: func()
	Life:Delete('clear,[]), // built-in crumple/trash can effect
}
----------
Life.status+nextButton
{_proto: protoTextButton,
viewBounds: :ButtonBounds(40), // second button
viewJustify: 8389638, // vjParentLeftH + vjParentTopV + vjSiblingRightH + oneLineOnly + vjCenterH + vjCenterV,
text: "Next",
buttonClickScript: func()
	if stopped
	then begin
		:nextGeneration();
		drawArea:dirty();
	end,
}
----------
Life.status+repeatButton
{_proto: protoTextButton,
viewBounds: :ButtonBounds(40), // third button
viewJustify: 8389638,
text: "Repeat",

buttonClickScript: func()
	if firstRow < kFirstRowCol // not empty?
	then if stopped 
		then begin // start up background process
			gc(); time1 := Ticks();
			stopped := Life:setupIdle(delayMsecs); // start background process. NIL
			SetValue(self, 'text, "Stop");
			end
		else :stop(true),

stop: func(fl)
	begin // used to stop things from repeatButton.buttonClickScript or viewIdleScript
		if fl
		then Life:SetupIdle(0);  // halt background process
		if nCount
		then begin
			Print("time:" && Ticks() - time1);
			nGen := 0;
			end;
       	stopped := TRUE;
		SetValue(self, 'text, "Repeat");
		drawArea:dirty(); // redraw with grid
		NIL; // turn off idling by returning nil
	end,
time1: NIL, // for possible timing (if nCount non-nil)
}
----------
Life.status+zhelpButton
{_proto: protoTextButton,
viewBounds: :ButtonBounds(40), // fourth button (zhelp sorts 4th)
viewJustify: 8389638,
text: "Info",
buttonClickScript: func()
	if helpBook and stopped // open help book
	then begin
		local TT := GetRoot().TinyTim; // the help reader app
		if not helpView
		then helpView := BuildContext(
			{_proto: TT._proto,
			bookRef: helpBook});
		TT:Close();   // in case system help is currently open
		helpView:openManual(helpBook);
		end,
}
----------
Life.helpBook+page1
.# first help book page.  this is much easier than dealing with BookMaker!
.subject 1
Describe Life
.story
Life is a mathematical simulation game described by John Conway in Scientific American in Oct. 1970.

Several rules are used to create a next generation from a cell's 8 neighbors:
- Survival: A cell with 2 or 3 neighbors survives.
- Death: A cell with 1 or less, or 4 or more neighbors, "dies".
- Birth: An empty cell with exactly 3 neighbors will be "born".
----------
Life.helpBook+page2
.subject 1
Understand the Code
.story
This version of Life (1.4) has six variations/optimizations, which are explained in the file: life.txt.

The article "Life with NewtonScript" by David Betz in Byte, March 1994, pp. 191-194 provides some introduction/background.

You could also register for the Newt Development Environment (see Develop) to obtain a Newton programming manual and more NewtonScript application examples.
----------
Life.helpBook+page3
.subject 1
Distribute
.story
This version of Life is freeware and may be distributed freely as long as all of the files are included and unmodified.  You are free to make modifications for your own use.

Copyright 1994-96, S. Weyer. All Rights Reserved Worldwide.
----------
Life.helpBook+page4
.subject 1
Use
.story
\uFC01\u grid: grid size
\uFC01\u method: computation method
\uFC01\u pattern: fill pattern for <tap>
\uFC01\u gen: turn on/off counter,timing

<tap>: add a pattern, or toggle a cell
<line>: fill an area
<scrub>: erase an area

[Clear]: clear all cells, reset Gen. counter
[Next]: evolve a single generation
[Repeat]: evolve until all cells are empty or you tap Stop or Clear
[Info]: this help book
----------
Life.helpBook+page5
.subject 1
Develop
.story
If you are interested in object-oriented programming, developing applications in NewtonScript and saving as packages directly on your Newton, try the "Newt" development environment.

Registered users receive a 80+ pp. manual (Acrobat or paper) and 190+ examples.

For further info, see life.txt or http://www.netaxs.com/~weyer/newton/releases.html 
----------
Life.helpBook+page6
.subject 1
Contact Author
.story
Steve Weyer
17 Timber Knoll Drive
Washington Crossing, PA 18977-1052
Internet: weyer@netaxs.com
America Online, eWorld, NewtonMail: SteveWeyer
Compuserve: 74603,2051

http://www.netaxs.com/~weyer/newton/releases.html
----------
BYE!
		
